import { record as recordCommand } from './CommandRecorder';

// Window interface for Geppetto is declared in index.tsx

interface PythonMessageData {
  msg_type: string;
  content: {
    evalue?: string;
    traceback?: string[];
    data?: {
      'text/plain'?: string;
    };
    text?: string;
  };
  parent_header?: {
    msg_id?: string;
  };
}

interface GeppettoEventData {
  id?: string;
  type: string;
  response: any;
}

const handle_output = function (data: PythonMessageData): void {
  // data is the object passed to the callback from the kernel execution
  switch (data.msg_type) {
    case 'error':
      console.log('ERROR while executing a Python command:');
      console.log(data.content.evalue?.trim());
      console.error('ERROR while executing a Python command:');
      console.error(data.content.traceback);
      if (data.content.evalue === "name 'utils' is not defined") {
        execPythonMessage('from jupyter_geppetto import synchronization, utils, synchronization as jupyter_geppetto');
        execPythonMessage('from netpyne_ui.netpyne_geppetto import netpyne_geppetto');
      }
      // dispatch(GeppettoActions.geppettoError);
      if (window.GEPPETTO) {
        window.GEPPETTO.trigger(window.GEPPETTO.Events.Error_while_exec_python_command, data.content);
      }
      break;
    case 'execute_result':
      let response: any;
      try {
        const textPlain = data.content.data?.['text/plain'];
        if (textPlain) {
          const cleaned = textPlain.replace(/^'(.*)'$/, '$1');
          response = JSON.parse(cleaned);
        } else {
          response = null;
        }
      } catch (error) {
        const textPlain = data.content.data?.['text/plain'];
        response = textPlain ? textPlain.replace(/^'(.*)'$/, '$1') : null;
      }
      if (window.GEPPETTO && data.parent_header?.msg_id) {
        window.GEPPETTO.trigger(window.GEPPETTO.Events.Receive_Python_Message, {
          id: data.parent_header.msg_id,
          type: data.msg_type,
          response,
        });
      }
      break;
    case 'display_data':
      // FIXME
      break;
    default:
      console.log(data.content.text?.trim(), true);
  }
};

const execPythonMessage = function (
  command: string,
  callback: (data: PythonMessageData) => void = handle_output,
  record: boolean = true
): Promise<any> {
  if (!window.IPython?.notebook?.kernel) {
    return Promise.reject(new Error('IPython kernel not available'));
  }

  const { kernel } = window.IPython.notebook;
  if (record) {
    recordCommand(kernel.id, command);
  }

  const messageID = kernel.execute(
    command,
    {
      iopub: { output: callback },
    },
    {
      silent: false,
      stop_on_error: true,
      store_history: true,
      netpyne_ui_triggered: true,
    }
  );

  return new Promise((resolve, reject) => {
    if (!window.GEPPETTO) {
      reject(new Error('GEPPETTO not available'));
      return;
    }

    const handler = (data: { data: GeppettoEventData }) => {
      if (data.data.id === messageID) {
        window.GEPPETTO.off(window.GEPPETTO.Events.Receive_Python_Message, handler);
        resolve(data.data.response);
      }
    };

    window.GEPPETTO.on(window.GEPPETTO.Events.Receive_Python_Message, handler);
  });
};

const execPythonMessageWithoutRecording = function (
  command: string,
  callback: (data: PythonMessageData) => void = handle_output
): Promise<any> {
  return execPythonMessage(command, callback, false);
};

const addslashes = function (str: string): string {
  return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
};

const evalPythonMessage = function (
  command: string,
  parameters: any[],
  parse: boolean = true,
  record: boolean = true
): Promise<any> {
  let parametersString = '';
  if (parameters) {
    if (parameters.length > 0) {
      parametersString = `(${parameters
        .map((parameter) => `utils.convertToPython('${addslashes(JSON.stringify(parameter))}')`)
        .join(',')})`;
    } else {
      parametersString = '()';
    }
  }

  let finalCommand = command + parametersString;
  if (parse) {
    finalCommand = `utils.convertToJS(${finalCommand})`;
  }
  return execPythonMessage(finalCommand, handle_output, record);
};

export { execPythonMessage, evalPythonMessage, execPythonMessageWithoutRecording };

